/*
 * @Author: wwssaabb
 * @Date: 2021-10-29 10:34:13
 * @LastEditTime: 2021-12-04 10:54:48
 * @FilePath: \handwritten-code\vue\Observer.js
 */

const {
  arrayMethods
} = required('./array.js')
import Dep from './dep'

//观察者对象
class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0

    //传入的value对象或数组添加__ob__对象，表示为响应式数据
    def(value, '__ob__', this)
    // Object.defineProperty(value, '__ob__', {
    //   value: this, //指向实例对象
    //   enumerable: false, //不可枚举
    //   writable: true, //可使用赋值运算符改写__ob__
    //   configurable: true, //可改写可删除
    // })

    //对象和数组分开处理
    if (Array.isArray(value)) {
      //如果是数组的话修改数组的原型，里面重写了数组的七个方法
      value.__proto__ = arrayMethods
      //对数组进行响应式处理
      this.observerArray(value)
    } else {
      //如果是对象使用walk进行响应式处理
      this.walk(value)
    }
  }

  //遍历对象的属性进行响应式处理
  walk(data) {
    let keys = Object.keys(data)
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const value = data[key]
      //传入data对象、key、value进行响应式处理
      defineReactive(data, key, value)
    }
  }

  //遍历数组对象进行响应式处理
  observerArray(items) {
    for (let i = 0; i < items.length; i++) {
      observer(items[i])
    }
  }
}

function defineReactive(data, key, value) {
  //value有可能有对象或数组，需递归处理
  // observer(value)
  //实例化一个dep，想当于创建一个依赖管理
  const dep = new Dep()

  var property = Object.getOwnPropertyDescriptor(data, key)

  //如果属性不可配置则返回
  if (property && property.configurable === false) return

  var getter = property && property.get
  var setter = property && property.set

  //针对深层次对象
  var childOb = !shallow && observer(value)
  //响应式核心
  //拦截对象里的每个属性的get和set，进行读写监听，响应式的底层原理
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log('get 获取值')
      return value
    },
    set(newValue) {
      if (newValue === value) return
      console.log('set 设置值')
      value = newValue
    }
  })
}

function observer(value) {
  //判断传进来的是对象或数组则进行响应式处理
  if (Object.prototype.toString.call(value) === '[object Object]' || Array.isArray(value)) return new Observer(value)
}

function def(obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    value,
    enumerable: !!enumerable,
    writeable: true,
    configurable: true,
  })
}

//为什么对象和数组要分开进行处理？
/* 
  基于性能消耗上的考虑，因为对象的属性对于数组的元素来讲，数量少，如果对其进行get和set的劫持不会消耗太多的性能，
  而数组的元素一般有很多，如果也对每一个元素进行劫持的话会比对象消耗更多的性能。所以数组的响应式是通过改写数组的
  七个修改数组自身的方法（push、pop、shift、unshift、reserve、sort、splice）来实现数组响应式的触发
*/

//vue响应式数据的一些遗留问题
/* 
  Object.defineProperty只能监听已经存在的属性变化，如果添加或删除属性不会发生响应式更新，所以vue就提供了$set添加
  属性、$delete删除属性
  数组通过索引值下标进行修改，无法触发响应式更新，需要使用splice进行修改操作触发，另外其余的六个能改变数组自身的函数
  也能也能触发响应式更新
*/

//什么时候进行响应式数据处理？
/* 
  new Vue() 之后， 将要进行初始化配置项和数据监测， 调用initState进行初始化状态
  （initState函数按顺序初始化props（initProps）、methods（initMethods）、data（initData）、computed（initComputed）、watch（initWatch）），
  initData就是用来初始化data，也就是进行响应式数据的处理
*/